const types = require('../../types');
const Scope = require('./Scope');
const generate = require('../../generator');

// 首先我们创建一个 path 的类，记录当前节点 node，父节点 parent 以及父节点的 path。
module.exports = class NodePath {
    constructor(node, parent, parentPath, key, listKey) {
        this.node = node;
        this.parent = parent;
        this.parentPath = parentPath;
        this.key = key;
        this.listKey = listKey;
        Object.keys(types).forEach(key => {
            if (key.startsWith('is')) {
                this[key] = types[key].bind(this, node);
            }
        })
    }
    // 接下来是实现 api，path 的 api 就是对 AST 的增删改，
    // 我们实现下 replaceWith、remove、findParent、find、traverse、skip 这些 api。


    // replaceWith 就是在父节点替换当前节点为另一个节点。
    // 但是我们现在并不知道当前节点在父节点的什么属性上，所以在遍历的时候要记录属性名的信息。
    // 这里要记录两个属性 key 和 listkey，比如如果属性是数组的话就要记录 key 是啥属性、listkey 是啥下标。
    // 比如 params 下的 Identifier 节点，key 是 params，listkey 是 1、2、3。
    // 如果不是数组的话，listkey 为空。
    //  key 和 listkey 就是为了实现对 AST 增删改的 api 用的。

    // path.replaceWith
    replaceWith(node) {
        if (this.listKey != undefined) {
            this.parent[this.key].splice(this.listKey, 1, node);
        } else {
            this.parent[this.key] = node
        }
    }

    // path.remove
    remove() {
        if (this.listKey != undefined) {
            this.parent[this.key].splice(this.listKey, 1);
        } else {
            this.parent[this.key] = null;
        }
    }

    // path.find、 path.findParent
    // find 和 findParent 是顺着 path 链向上查找 AST，并且把节点传入回调函数，
    // 如果找到了就返回节点的 path。区别是 find 包含当前节点，findParent 不包含。
    findParent(callback) {
        let curPath = this.parentPath;
        while (curPath && !callback(curPath)) {
            curPath = curPath.parentPath;
        }
        return curPath;
    }

    find(callback) {
        let curPath = this;
        while (curPath && !callback(curPath)) {
            curPath = curPath.parentPath;
        }
        return curPath;
    }

    // path.traverse
    // traverse 的 api 是基于上面实现的 traverse
    // 但是有一点不同，path.traverse 不需要再遍历当前节点，直接遍历子节点即可。
    traverse(visitors) {
        const traverse = require('../index');
        const defination = types.visitorKeys.get(this.node.type);
        if (defination.visitor) {
            defination.visitor.forEach(key => {
                const prop = this.node[key];
                // 如果该属性是数组
                if (Array.isArray(prop)) {
                    prop.forEach((childNode, index) => {
                        traverse(childNode, visitors, this.node, this);
                    })
                } else {
                    traverse(prop, visitors, this.node, this);
                }
            })
        }
    }

    // path.skip
    // skip 的实现可以给节点加个标记，遍历的过程中如果发现了这个标记就跳过子节点遍历。
    skip() {
        this.node.__shouldSkip = true;
    }


    // path.toString
    // toString 是把当前节点打印成目标代码，会调用 generator
    toString() {
        return generate(this.node).code;
    }

    // 当需要用到 scope 的时候才会创建，因为 scope 创建之后还要遍历查找 bindings，是比较耗时的，
    // 实现 get 可以做到用到的时候才创建。
    get scope() {
        if (this.__scope) {
            return this.__scope;
        }
        const isBlock = this.isBlock();
        const parentScope = this.parentPath && this.parentPath.scope;
        return this.__scope = isBlock ? new Scope(parentScope, this) : parentScope;
    }

    isBlock() {
        return types.visitorKeys.get(this.node.type).isBlock;
    }


}